﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Net;

namespace ERF {
    /// <summary>
    /// Abstract class for networkhandling. Adds events for gamehosted, gamejoined and gameleft.
    /// </summary>
    abstract public class NetworkHandler : GameComponent {
        // Max network activity in bytes/second.
        private const short MAX_NETWORK_ACTIVITY = 8000;
        // Min delay for sending data.
        private const float MIN_NETWORK_DELAY = 0.05f;
        private float time;
        protected NetworkSession networkSession;
        protected PacketReader packetReader;
        protected PacketWriter packetWriter;
        public event EventHandler<GamerJoinedEventArgs> GamerJoined;
        public event EventHandler<GamerLeftEventArgs> GamerLeft;
        public event EventHandler<GameStartedEventArgs> GameStarted;
        public event EventHandler<GameEndedEventArgs> GameEnded;
        public event EventHandler<HostChangedEventArgs> HostChanged;
        public event EventHandler GameHosted;
        public event EventHandler GameJoined;
        public event EventHandler GameLeft;
        public NetworkHandler(ERFGame game)
            : base(game) {
            this.packetReader = new PacketReader();
            this.packetWriter = new PacketWriter();
            game.Components.Add(this);
        }

        /// <summary>
        /// Binds events to events for passing through.
        /// </summary>
        /// <param name="session"></param>
        private void HookSession(NetworkSession session) {
            session.GamerJoined += this.GamerJoined;
            session.GamerLeft += this.GamerLeft;
            session.GameStarted += this.GameStarted;
            session.GameEnded += this.GameEnded;
            session.HostChanged += this.HostChanged;
        }

        /// <summary>
        /// Hosts a networkgame
        /// </summary>
        /// <param name="type"></param>
        /// <param name="local"></param>
        /// <param name="gamers"></param>
        public void HostGame(NetworkSessionType type, int local, int gamers) {
            if (this.networkSession == null)
                if (SignedInGamer.SignedInGamers.Count > 0) {
                    this.networkSession = NetworkSession.Create(type, local, gamers);
                    this.HookSession(this.networkSession);
                    this.GameHosted(this, EventArgs.Empty);
                } else
                    GameConsole.GetInstance.AddMessage("You need to sign in to Live before hosting a game!");
        }
        
        /// <summary>
        /// Finds all available networkgames.
        /// </summary>
        /// <returns></returns>
        public AvailableNetworkSessionCollection FindGames() {
            if (this.networkSession == null)
                if (SignedInGamer.SignedInGamers.Count > 0)
                    return NetworkSession.Find(NetworkSessionType.SystemLink, 1, null);
                else
                    throw new Exception("Sign in user needed before searching for games.");
            throw new Exception("Network session needed before searching for games.");
        }
        /// <summary>
        /// Joins an networkgame found in session passed
        /// </summary>
        /// <param name="session"></param>
        public void JoinGame(AvailableNetworkSession session) {
            if (this.networkSession == null)
                if (SignedInGamer.SignedInGamers.Count > 0) {
                    this.networkSession = NetworkSession.Join(session);
                    this.HookSession(this.networkSession);
                    this.GameJoined(this, EventArgs.Empty);
                } else
                    GameConsole.GetInstance.AddMessage("You need to sign in to Live before joining a game!");
        }

        public void LeaveGame()
        {
            if (this.networkSession != null)
            {
                /*
                if (this.networkSession.IsHost)
                    this.GameEnded(this, new GameEndedEventArgs());
                else
                    this.GameLeft(this, EventArgs.Empty);
                 */
                this.networkSession.Dispose();
                this.networkSession = null;
            }
        }

        /// <summary>
        /// Called when gamer has data to reciev from network players.
        /// </summary>
        /// <param name="gamer"></param>
        protected abstract void RecieveData(LocalNetworkGamer gamer);
        /// <summary>
        /// Called when gamer can send data to other network players.
        /// </summary>
        /// <param name="gamer"></param>
        protected abstract void SendData(LocalNetworkGamer gamer);

        public override void Update(GameTime gameTime) {
            if (this.networkSession != null) {
                this.time += (float)gameTime.ElapsedGameTime.TotalSeconds;
                networkSession.Update();
                foreach(LocalNetworkGamer gamer in this.networkSession.LocalGamers)
                    while (gamer.IsDataAvailable) this.RecieveData(gamer);
                if (this.time > MIN_NETWORK_DELAY) {
                    foreach (LocalNetworkGamer gamer in this.networkSession.LocalGamers)
                        if (this.networkSession.BytesPerSecondSent + this.networkSession.BytesPerSecondReceived < MAX_NETWORK_ACTIVITY)
                            this.SendData(gamer);
                    this.time = 0;
                }
            }
            base.Update(gameTime);
        }
    }
}
